#include <string>
#include <cassert>
#include <sstream>
#include <iostream>

#include <unistd.h>
#include <pthread.h>

struct Point {

  Point(): Point(0, 0) {}

  Point(int x, int y) : x(x), y(y) {}

  std::string ToString() const {
    std::ostringstream oss;
    oss << "Point:{" << x << "," << y << "}";
    return oss.str();
  }

  int x;
  int y;
};


struct NoticeCenter {
public:
    NoticeCenter() {
        assert(0 == pthread_rwlock_init(&lock_, nullptr));
    }
    ~NoticeCenter() {
        assert(0 == pthread_rwlock_destroy(&lock_));
    }

    void publish(const Point & point) {
        assert(0 == pthread_rwlock_wrlock(&lock_));

        pthread_t tid = pthread_self();
        printf("thread [0x%04x] going to update %s\n", (unsigned int)tid & 0xffff, point.ToString().c_str());
        point_ = point;

        assert(0 == pthread_rwlock_unlock(&lock_));
        printf("thread [0x%04x] updated over\n", (unsigned int)tid & 0xffff);
    }

    void read(Point & point) {
        assert(0 == pthread_rwlock_rdlock(&lock_));

        pthread_t tid = pthread_self();
        printf("thread [0x%04x] going to read\n", (unsigned int)tid & 0xffff);
        point = point_; 
        usleep(50);

        assert(0 == pthread_rwlock_unlock(&lock_));
        printf("thread [0x%04x] read over\n", (unsigned int)tid & 0xffff);
    }

private:
    Point point_;
    pthread_rwlock_t lock_;
};


class NoticePublisher {
public:
    NoticePublisher(NoticeCenter & noticeCenter) : center(noticeCenter) {
    }
    ~NoticePublisher() {}

    void start() {
        assert(0 == pthread_create(&tid, nullptr, publish, this));
    }

    void join() {
        assert(0 == pthread_join(tid, nullptr));
    }

private:
    static void * publish(void * arg){
        NoticePublisher * pThis = static_cast<NoticePublisher *>(arg);
        assert(nullptr != pThis);

        const int MAX_INC_COUNT = 5;
        for(int i = 1; i < MAX_INC_COUNT; ++i) {
            {
                pThis->point.x += 1;
                pThis->point.y += 2;
            }
            pThis->center.publish(pThis->point);
            sleep(1);
        }

        printf("Publisher thread [0x%04x] exit\n", (unsigned int)(pThis->tid) & 0xffff);
        return nullptr;
    }

private:
    NoticeCenter & center;
    Point point;
    pthread_t tid;
};


class NoticeReaders {
public:
    NoticeReaders(NoticeCenter & noticeCenter) : center(noticeCenter) {
    }
    ~NoticeReaders() {}

    void start(){
        for(int i = 0; i < READER_NUMBERS; ++i){
            assert(0 == pthread_create(tids+i, nullptr, read, &center));
        }
    }

    void join(){
        for(int i =0; i < READER_NUMBERS; ++i){
            assert(0 == pthread_join(tids[i], nullptr));
        }
    }

private:
    static void * read(void * arg){
        NoticeCenter * center = static_cast<NoticeCenter *>(arg);
        assert(nullptr != center);

        pthread_t tid = pthread_self();
    
        Point local;
        const int MAX_READ_COUNT = 10;
        for(int i = 1; i < MAX_READ_COUNT; ++i) {
            center->read(local);
            printf("Reader thread [0x%04x] read %s\n", (unsigned int)tid & 0xffff, local.ToString().c_str());
            usleep(500000);
        }

        printf("Reader thread [0x%04x] exit\n", (unsigned int)tid & 0xffff);
        return nullptr;
    }

private:
    NoticeCenter & center;
    static const int READER_NUMBERS = 3;
    pthread_t tids[READER_NUMBERS];
};

int main(){
    NoticeCenter center;
    NoticePublisher publisher(center);
    NoticeReaders readers(center);

    publisher.start();
    readers.start();

    publisher.join();
    readers.join();

    pthread_t  tid = pthread_self();
    printf("main thread [0x%04x] exit\n",(unsigned int)tid & 0xffff);
    return 0;
}
